package com.naiterui.ehp.bs.doctor.service.impl;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import com.naiterui.ehp.bp.bo.doctor.LoginParamBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.Login;
import com.naiterui.ehp.bp.domain.PatientGroup;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.PublicService;
import com.naiterui.ehp.bp.support.security.HeaderConstant;
import com.naiterui.ehp.bp.support.utils.IpUtil;
import com.naiterui.ehp.bp.utils.context.SpringContextHolder;
import com.naiterui.ehp.bp.utils.encypt.DESPlus;
import com.naiterui.ehp.bp.utils.encypt.GengerCode;
import com.naiterui.ehp.bp.utils.encypt.MD5;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.notice.AppNoticeUtil;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.doctor.dao.*;
import com.naiterui.ehp.bs.doctor.domain.AgAgentCustom;
import com.naiterui.ehp.bs.doctor.domain.LoginLog;
import com.naiterui.ehp.bs.doctor.exception.ExceptionCodes;
import com.naiterui.ehp.bs.doctor.interceptor.PasswordExpireInterceptor;
import com.naiterui.ehp.bs.doctor.repository.IAgentCustomRepository;
import com.naiterui.ehp.bs.doctor.repository.ILoginLogRepository;
import com.naiterui.ehp.bs.doctor.service.*;
import com.naiterui.ehp.bs.doctor.utils.AdTencentCloudSdkUtil;
import com.naiterui.ehp.bs.doctor.utils.PropertyValueConstants;
import com.naiterui.ehp.bs.doctor.utils.RedisUtils;
import com.naiterui.ehp.bs.doctor.utils.SMSUtil;
import com.naiterui.ehp.bs.doctor.vo.DeviceVO;
import com.naiterui.ehp.bs.doctor.vo.DeviceVerifyVO;
import com.naiterui.ehp.bs.doctor.vo.LoginVO;
import com.naiterui.ehp.bs.doctor.vo.TencentFaceIdVO;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class AdLoginServiceImpl implements ILoginService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AdLoginServiceImpl.class);
    @Autowired
    private ILoginDao loginDao;
    @Autowired
    private ILoginLogRepository loginLogRepository;
    @Autowired
    private IDoctorDao doctorDao;
    @Autowired
    private IQuickReplyDao quickReplyDao;
    @Autowired
    private IDrBayConsultDao bayConsultDao;
    @Autowired
    private IPatientGroupDao patientGroupDao;
    @Autowired
    private IDoctorService doctorService;
    @Autowired
    private IDrSerItemsService drChargeItemsService;
    @Autowired
    private ITencentDetectService tencentDetectService;

    @Autowired
    private IBeforePasswordService beforePasswordService;
    @Autowired
    private IAgentCustomRepository agentCustomRepository;

    @Override
    public boolean findCountByPhoneNum(String phoneNum) {

        return this.loginDao.findCountByPhoneNum(phoneNum);
    }

    private Long saveLoginInfo(Login login, String name) throws BusinessException {
        if (login == null) {
            LOGGER.warn("login对象为空");
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }

        login.setLastPwdModify(new Date());

        // 组织合作方(公司)id
        /*Long partnerId = null;
        if (null != login && PropertyValueConstants.INVTEE_TYPE_BUSINESS == login.getType() && null != login.getRelationId()) {
            BusinessDeputy business = businessDeputyDao.get(login.getRelationId());
            partnerId = business.getPartnerId();
        }
        // TODO 缓存处理,添加更新处理
        if (null == partnerId) {
            BsPartner partner = partnerDao.getDefaultPartner();
            partnerId = null == partner ? 0L : partner.getId();
        }*/

        // 创建登陆对象
        Long loginId = this.loginDao.saveLogin(login);
        Doctor doctor = new Doctor();
        // 设置用户id
        if (name != null) {
            doctor.setName(name.trim());
        } else {
            doctor.setName("互联网医院");
        }
        doctor.setUserId(loginId);
        doctor.setPhone(login.getPhoneNum());
        doctor.setRecommCode(login.getRecommCode());
        //doctor.setPartnerId(partnerId);
        doctor.setStatus(Doctor.AUTH_STATUS_NOT_PERFECTION);
        doctor.setAccountStatus(0);
        doctor.setLabelType(Doctor.DOCTOR_LABLE_NOMAL);
        doctor.setCreatedAt(new Date());
        doctor.setType(PropertyValueConstants.DOCTOR_TYPE_NOMAL);
        doctor.setRecordStatus(Doctor.RECORD_STATUS_NOT_APPLY);

        Long doctorId = this.doctorDao.save(doctor);

        // 初始化用户积分总表
        //this.drIntegralService.saveInitPoint(doctorId);

        // 初始化已开启收费项目
        this.drChargeItemsService.saveInitServiceItems(doctor);

        return doctorId;
    }

    @Override
    public void updateLogin(Login login) {
        this.loginDao.updateLogin(login);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Login findLoginByPhoneNum(String phoneNum) {

        return this.loginDao.findLoginByPhoneNum(phoneNum);
    }

    @Override
    public Login findLoginById(Long loginId) {
        return this.loginDao.get(loginId);
    }

    private boolean findExsitSaltRecomm(String salt, String recommCode) {
        return this.loginDao.findExsitSaltRecomm(salt, recommCode);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveInitDoctor(Long doctorId) {

        // 初始化快捷回复模板信息
        List<String> replyList = new ArrayList<>();
        String reply1 = ConfigUtil.getString("quick.reply1");
        if (StringUtils.isNoneBlank(reply1)) {
            replyList.add(reply1);
        }
        String reply2 = ConfigUtil.getString("quick.reply2");
        if (StringUtils.isNoneBlank(reply2)) {
            replyList.add(reply2);
        }
        String reply3 = ConfigUtil.getString("quick.reply3");
        if (StringUtils.isNoneBlank(reply3)) {
            replyList.add(reply3);
        }
        String reply4 = ConfigUtil.getString("quick.reply4");
        if (StringUtils.isNoneBlank(reply4)) {
            replyList.add(reply4);
        }
        String reply5 = ConfigUtil.getString("quick.reply5");
        if (StringUtils.isNoneBlank(reply5)) {
            replyList.add(reply5);
        }
        String reply6 = ConfigUtil.getString("quick.reply6");
        if (StringUtils.isNoneBlank(reply6)) {
            replyList.add(reply6);
        }
        this.quickReplyDao.saveBatch(replyList, doctorId);

        // 初始化医生工作时间
        String week = ConfigUtil.getString("naiterui.week");
        String everyTime = ConfigUtil.getString("naiterui.every.time");
        this.bayConsultDao.saveBatch(doctorId, week, everyTime);

        // 添加默认分组
        String groupNames = ConfigUtil.getString("naiterui.group.name");
        if (groupNames != null) {
            String[] names = groupNames.split(",");
            for (String name : names) {
                // 创建新分组
                PatientGroup group = new PatientGroup();
                group.setName(name);
                group.setDoctorId(doctorId);
                group.setCreatedAt(new Date());
                this.patientGroupDao.save(group);
            }
        }

    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void savePassword(Long doctorId, Login login, String pwd) throws BusinessException {
        login.setPassword(MD5.md5(pwd + login.getSalt()));
        login.setChangedAt(new Date(System.currentTimeMillis()));
        login.setLastPwdModify(new Date());

        // 校验曾用密码
        Integer norepetTimes = ConfigUtil.getInt("dr.password.norepet.times");

        if (norepetTimes != null && norepetTimes > 0) {

            // 校验曾用密码
            boolean repet = this.beforePasswordService.verifyRepetBeforePassword(login.getId(), pwd, login.getSalt(), norepetTimes);
            if (repet) {
                LOGGER.warn("当前密码近期使用过，请重新设置密码 userId:{}", login.getId());
                throw new BusinessException(ExceptionCodes.PWD_NOREPET);
            }
            // 保存最近密码
            this.beforePasswordService.saveBeforePassword(login.getId(), pwd, login.getSalt());
        }

        // 重新设置密码最后修改时间缓存
        PasswordExpireInterceptor.setDoctorLastPasswordModifyCache(doctorId, login.getLastPwdModify());
        this.updateLogin(login);
    }

    /**
     * 更新登录信息
     * LoginServiceImpl.updateDeviceSN()
     *
     * @param deviceSN
     * @param doctorId
     * @throws BusinessException
     * @Author guoyongxiang
     * @Date 2016年1月11日
     * @since 1.0.0
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public DeviceVO updateDeviceSN(String platform, String version, String model, String deviceSN, Long doctorId, String loginAddr, String origin)
            throws BusinessException {
        DeviceVO device = null;
        boolean verify = PublicService.paramsHasNull(deviceSN, model);
        if (!verify) {
            device = new DeviceVO(deviceSN, model, loginAddr);
            RedisUtils.set(PropertyValueConstants.DR_DSN + doctorId, device);
        } else {
            LOGGER.info("设备号或机型为空，医生id:" + doctorId + ",deviceSN:" + deviceSN + ",_model:" + model + ",_v:" + version);
            throw new BusinessException(ExceptionCodes.PARAM_ERROR);
        }
        return device;

    }

    /**
     * 获取校验信息
     * LoginServiceImpl.getVerifyInfo()
     *
     * @param doctorId
     * @param deviceSN
     * @return
     * @throws BusinessException
     * @Author guoyongxiang
     * @Date 2016年1月11日
     * @since 1.0.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public DeviceVerifyVO getVerifyInfo(Long doctorId, String deviceSN) throws BusinessException {
        DeviceVerifyVO verify = new DeviceVerifyVO();
        DeviceVO vo = (DeviceVO) RedisUtils.get(PropertyValueConstants.DR_DSN + doctorId);
        if (vo == null) {
            Doctor doctor = this.doctorDao.get(doctorId);
            if (doctor == null) {
                throw new BusinessException(ExceptionCodes.DOCTOR_NOT_EXIST);
            }
            Login login = this.loginDao.get(doctor.getUserId());
            if (login != null) {
                vo = new DeviceVO(login.getDeviceSN(), login.getModel(), login.getLoginAddr());
                RedisUtils.set(PropertyValueConstants.DR_DSN + doctorId, vo);
            } else {
                throw new BusinessException(ExceptionCodes.DOCTOR_NOT_EXIST);
            }
        }
        if (deviceSN.equals(vo.getDeviceSN())) {
            verify = new DeviceVerifyVO(false, vo.getCreatedAt(), vo.getModel(), vo.getLoginAddr());
        } else {
            verify = new DeviceVerifyVO(true, vo.getCreatedAt(), vo.getModel(), vo.getLoginAddr());
        }
        return verify;
    }

    // 判断手机号是否已经注册
    private void validExistsPhone(String phoneNum) throws BusinessException {
        // 电话是已经存在
        boolean exsit = this.findCountByPhoneNum(phoneNum);
        if (exsit) {
            throw new BusinessException(ExceptionCodes.REGIST_EXSIT);
        }
    }

    // 封装登录用户信息
    private Login validLogin(String phoneNum, String pwd, Integer relationType, Long relationId, String salt, String recommCode,
                             Integer registSource) {

        Login login = new Login();
        login.setPhoneNum(phoneNum);
        login.setPassword(MD5.md5(pwd + salt));
        login.setType(relationType);
        login.setRelationId(relationId);
        login.setSalt(salt);
        login.setRecommCode(recommCode);
        login.setRegType(registSource);
        login.setCreatedAt(new Date(System.currentTimeMillis()));
        login.setLastPwdModify(new Date());
        return login;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public LoginVO saveLoginInfoById(Long loginId, String loginKey, String password, String version, String platform, String deviceSN,
                                     HttpServletRequest request) throws BusinessException {
        // 1.根据医生loginId获取登录信息
        Login login = this.findLoginById(loginId);
        if (login == null) {
            LOGGER.error("登陆获取医生数据异常,loginId={}", loginId);
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        String loginFailKey = PropertyValueConstants.getLoginFailKey(loginId);
        String loginFailValue = RedisUtils.getString(loginFailKey);
        if (StringUtils.isNotBlank(loginFailValue) && Integer.valueOf(loginFailValue) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
        }
        // 2.验证登陆key
        try {
            String cacheLoginKey = PropertyValueConstants.DOCTOR_LOGIN_KEY + String.valueOf(loginId);
            String cacheValue = (String) RedisUtils.get(cacheLoginKey);
            if (cacheValue == null || !cacheValue.equals(loginKey)) {
                LOGGER.error("登陆验证登陆key错误，客户端加密key=" + loginKey + "后台缓存加密后为:" + cacheValue + ",loginId=" + loginId);
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
            }
        } catch (Exception e) {
            LOGGER.error("向redis中缓存获取用户登陆token失败,loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT, e);
        }

        password = MD5.md5(password.trim() + login.getSalt());
        if (!password.equals(login.getPassword())) {
            RedisUtils.incre(loginFailKey);
            RedisUtils.expire(loginFailKey, PropertyValueConstants.LOGIN_FAIL_LOCK_TIME);
            if (Integer.valueOf(RedisUtils.getString(loginFailKey)) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
            }
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
        } else {
            RedisUtils.delete(loginFailKey);
        }

        // 3.如果医生信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, request);
    }

    @Override
    public LoginVO saveLoginInfoByPhone(String phoneNum, String version, String platform, String deviceSN, HttpServletRequest request)
            throws BusinessException {

        // 1.根据医生手机号获取登录信息
        Login login = this.findLoginByPhoneNum(phoneNum);

        // 2.当医生手机号不存在时，走注册流程，cache中记录验证成功，验证码存放10分钟,并提示用户未注册
        if (login == null) {
            String cacheKey = PropertyValueConstants.PHONE_VALID_REGIST_KEY + phoneNum;
            RedisUtils.set(cacheKey, "", PropertyValueConstants.SAVE_VERIFY_CODE);
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        // 3.如果医生信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, request);
    }

    @Override
    public LoginVO saveLoginInfoConbine(Long loginId, String loginKey, String password, String phoneNum, String version, String platform, String deviceSN,
                                        HttpServletRequest request) throws BusinessException {

        // 1.根据医生手机号获取登录信息
        Login login = this.findLoginByPhoneNum(phoneNum);

        // 2.当医生手机号不存在时，走注册流程，cache中记录验证成功，验证码存放10分钟,并提示用户未注册
        if (login == null) {
            String cacheKey = PropertyValueConstants.PHONE_VALID_REGIST_KEY + phoneNum;
            RedisUtils.set(cacheKey, "", PropertyValueConstants.SAVE_VERIFY_CODE);
            LOGGER.error("登陆获取医生数据异常,loginId={},phoneNum={}", loginId, phoneNum);
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        String loginFailKey = PropertyValueConstants.getLoginFailKey(loginId);
        String loginFailValue = RedisUtils.getString(loginFailKey);
        if (StringUtils.isNotBlank(loginFailValue) && Integer.parseInt(loginFailValue) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
        }
        // 2.验证登陆key
        try {
            String cacheLoginKey = PropertyValueConstants.DOCTOR_LOGIN_KEY + String.valueOf(loginId);
            String cacheValue = (String) RedisUtils.get(cacheLoginKey);
            if (cacheValue == null || !cacheValue.equals(loginKey)) {
                LOGGER.error("登陆验证登陆key错误，客户端加密key=" + loginKey + "后台缓存加密后为:" + cacheValue + ",loginId=" + loginId);
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
            }
        } catch (Exception e) {
            LOGGER.error("向redis中缓存获取用户登陆token失败,loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT, e);
        }

        password = MD5.md5(password.trim() + login.getSalt());
        if (!password.equals(login.getPassword())) {
            RedisUtils.incre(loginFailKey);
            RedisUtils.expire(loginFailKey, PropertyValueConstants.LOGIN_FAIL_LOCK_TIME);
            if (Integer.parseInt(RedisUtils.getString(loginFailKey)) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
            }
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
        } else {
            RedisUtils.delete(loginFailKey);
        }

        return this.handleLogin(login, version, platform, deviceSN, request);
    }

    // 处理登录逻辑，并返回用户相关信息
    private LoginVO handleLogin(Login login, String version, String platform, String deviceSN, HttpServletRequest request) throws BusinessException {

        // 上次用户登录设备信息
        String lastLoginDeviceSN = login.getDeviceSN();

        // 读取主库
        Doctor doctor = this.doctorService.findDoctorByUserId(login.getId());
        if (doctor == null) {
            LOGGER.error("登陆获取数据异常,登陆id为loginId=" + login.getId());
            throw new BusinessException(ExceptionCodes.USER_NOT_EXIST);
        }
        if (doctor.getAccountStatus() != null && doctor.getAccountStatus() != 0) {
            throw new BusinessException(ExceptionCodes.DOCTOR_ACCOUNT_NOT_NOMAL);
        }

        LoginVO loginVO = new LoginVO();

        // 拦截已备案医生进行人脸实名验证
        if (ConfigUtil.getInt("user.login.realname.switch") == 1) {
            if (Doctor.AUTH_STATUS_SUCCESS == doctor.getStatus()) {
                // 查询实名认证结果，如未查询到，则提示实名
                AdTencentCloudSdkUtil.FaceSyncVO faceSync = this.tencentDetectService.getFaceSync(doctor.getId());
                if (!faceSync.getResult()) {
                    LOGGER.warn("当前账户已完成备案，登录需进行实名认证 doctorId:{}", doctor.getId());
                    TencentFaceIdVO faceIdVO = this.tencentDetectService.getFaceId(doctor.getId());
                    loginVO.setTencentFaceAuth(faceIdVO);
                    throw new BusinessException(ExceptionCodes.LOGIN_NEED_VERIFY_AUTH, null, loginVO);
                }
            }
        }

        Long doctorId = doctor.getId();
        loginVO.setDoctorId(doctorId);

        // 添加医生状态返回
        Integer status = doctor.getStatus();
        loginVO.setStatus(status == null ? Doctor.AUTH_STATUS_NOT_PERFECTION : status);

        // 3.数据库回写token和最后登陆时间
        Date now = new Date();
        String token = DESPlus.gengerateLoginToken(login.getPhoneNum() + UUID.randomUUID());
        login.setToken(token);

        // 4.处理最后登录时间
        // 2.2.0处理首登初始化系统公告类型 change by Gaoll 2016-3-10
        Integer firstFlag = LoginVO.FIRST_FLAG_NORMAL;
        if (null == login.getLastLoginTime()) {
            firstFlag = LoginVO.FIRST_FLAG_NEW_USER;
            // 首次登陆保存未认证redis（72小时后短信、push提醒）
            // RedisUtils.hset(PropertyValueConstants.DOCTOR_AUDIT_REMIND_CACHE, doctorId.toString(), now.getTime() + "");
        } else {
            // firstFlag = this.paService.pushValuableNotice(version, login);
        }
        login.setLastLoginTime(now);

        String model = request.getHeader(HeaderConstant.HEADER_MODEL);
        String ip = IpUtil.getIp(request);
        String loginAddr = IpUtil.getCityInfo(ip);

        String origin = request.getHeader(HeaderConstant.HEADER_ORIGIN);
        DeviceVO device = this.updateDeviceSN(platform, version, model, deviceSN, doctor.getId(), loginAddr, origin);
        if (device != null) {
            login.setIp(ip);
            login.setLoginAddr(loginAddr);
            login.setDeviceSN(deviceSN);
            login.setModel(model);
        }
        this.updateLogin(login);
        // 登入日志
        this.saveLoginLog(login, doctor);

        LOGGER.info("[userLogin]loginId=" + login.getId() + ",doctorId=" + doctorId + ",ip=" + ip + ",loginAddr=" + loginAddr + ",model=" + model
                + ",deviceSN=" + deviceSN);

        try {
            String cacheKey = PropertyValueConstants.DOCTOR_LOGIN_TOKEN_KEY + doctorId;
            RedisUtils.set(cacheKey, token);
        } catch (Exception e) {
            LOGGER.error("向redis中缓存用户登陆token失败,doctorId={}", doctorId, e);
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_FAIL, e);
        }
        loginVO.setFastLoginToken(generateFastLonginToken(doctorId));
        loginVO.setToken(token);
        loginVO.setFirstFlag(firstFlag);
        // 2.0.1胡创让添加的判断当前登录设备是否和上次登录设备相同 by chenlin 20160414
        loginVO.setDeviceFlag(StringUtils.isNotBlank(lastLoginDeviceSN) && lastLoginDeviceSN.equals(deviceSN) ? 1 : 0);

        /*
         * 新用户第一次登录
         */
        if (LoginVO.FIRST_FLAG_NEW_USER.equals(firstFlag)) {
            // 发送问卷调查
            // this.sendQuestionnaire(doctorId, login.getPhoneNum(), now);

            // CDSS被邀请用户登录，获得使用权
            // this.saveCDSSAccount(doctorId, login.getPhoneNum());
        }

        return loginVO;
    }


    /**
     * 算验证错误次数并判断redisKey对应的错误验证码验证次数是否在规定时间内超过规定值，
     * 如果超过验证次数，则返回true,否则返回false.
     *
     * @param cacheKey
     * @return
     */
    @Override
    public boolean validUserOperator(String cacheKey) {
        // 根据业务cacheKey，计算出验证validCacheKey和validCacheKey
        String validCacheCountKey = PropertyValueConstants.getSmsValidUserOperatorKey(cacheKey);
        String validCacheTimeKey = cacheKey + "|time";
        // 缓存中获取验证次数记录周期
        Object avlTimeObj = RedisUtils.hget(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_TIME_FIELD);
        Integer avlTime = Integer.parseInt(avlTimeObj == null ? "0" : String.valueOf(avlTimeObj));
        if (avlTime == 0) {
            avlTime = PropertyValueConstants.DEFAULT_TIME_FIELD;
        }
        // validCacheTimeKey用于记录验证用户操作的周期
        // validCacheKey用于记录验证用户操作的次数
        if (!RedisUtils.existsKey(validCacheTimeKey)) {
            RedisUtils.set(validCacheTimeKey, "", avlTime);
            RedisUtils.delete(validCacheCountKey);
        }
        // 缓存中获取验证次数
        long validCount = RedisUtils.incre(validCacheCountKey);
        // 缓存中获取可操作次数
        Object avlCountObj = RedisUtils.hget(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_COUNT_FIELD);
        Integer avlCount = Integer.parseInt(avlCountObj == null ? "0" : String.valueOf(avlCountObj));
        if (avlCount == 0) {
            avlCount = PropertyValueConstants.DEFAULT_COUNT_FIELD;
        }
        LOGGER.info("短信验证码使用验证key：" + cacheKey + "验证错误次数：" + validCount + ",最大可输入的验证错误次数：" + avlCount);
        return validCount > avlCount;
    }

    /**
     * 判断redisKey对应的错误验证码验证次数是否在规定时间内超过规定值，如果超过验证次数，则返回true,否则返回false.
     */
    @Override
    public boolean validUserOperatorFlag(String cacheKey) {
        // 根据业务cacheKey，计算出验证validCacheKey和validCacheKey
        String validCacheTimeKey = cacheKey + "|time";
        // 错误次数的记录已经过期，则直接返回false
        if (!RedisUtils.existsKey(validCacheTimeKey)) {
            return false;
        }
        // 缓存中获取验证次数
        String validCacheCountKey = PropertyValueConstants.getSmsValidUserOperatorKey(cacheKey);
        String validCountStr = RedisUtils.getString(validCacheCountKey);
        long validCount = 0;
        if (StringUtils.isNotEmpty(validCountStr)) {
            validCount = Long.parseLong(validCountStr);
        }
        // 缓存中获取可操作次数
        Object avlCountObj = RedisUtils.hget(PropertyValueConstants.VALID_KEY, PropertyValueConstants.VALID_COUNT_FIELD);
        Integer avlCount = Integer.parseInt(avlCountObj == null ? "0" : String.valueOf(avlCountObj));
        if (avlCount == 0) {
            avlCount = PropertyValueConstants.DEFAULT_COUNT_FIELD;
        }
        LOGGER.info("短信验证码发送查询验证key：" + cacheKey + "验证错误次数：" + validCount + ",最大可输入的验证错误次数：" + avlCount);
        return validCount > avlCount;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void initRegist(Long doctorId, String phoneNum) {
        // 异步保存医生基础信息
        try {
            long docId = doctorId;
            ThreadPoolUtil.execute(() -> SpringContextHolder.getBean(ILoginService.class).saveInitDoctor(docId));
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("保存医生互联网医院出诊时间数据异常，phoneNum=" + phoneNum);
        }
    }

    /**
     * 抽离公用注册流程
     *
     * @param paramBO
     * @return
     * @throws BusinessException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long saveLogin(LoginParamBO paramBO) throws BusinessException {

        // TODO paramBO 增加参数校验
        String phoneNum = paramBO.getPhone();
        String pwd = paramBO.getPwd();
        Integer relationType = paramBO.getRelationType();
        Long relationId = paramBO.getRelationId();
        String name = paramBO.getName();
        boolean registH5Flag = paramBO.isScan();
        String platform = paramBO.getPlatform();

        // 1. 验证手机是否存在
        this.validExistsPhone(phoneNum);

        // 2.生成唯一的盐和邀请码
        String salt = null;
        String recommCode = null;
        do {
            salt = GengerCode.generateSaltCode();
            recommCode = GengerCode.generateDocCode();
        } while (this.findExsitSaltRecomm(salt, recommCode));

        // 3.判断注册渠道,并封装用户的注册信息
        Login login = this.validLogin(phoneNum, pwd, relationType, relationId, salt, recommCode, paramBO.getRegistSouce());
        // 4.保存医生注册信息
        Long doctorId = null;
        try {
            doctorId = this.saveLoginInfo(login, name);

        } catch (Exception e) {

            LOGGER.error("保存医生注册数据异常，phoneNum=" + phoneNum, e);
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        // 5.扫码注册时，需要维护关联上级关系
        if (registH5Flag) {
            LOGGER.info("医生通过h5扫码完成注册，医生手机号phoneNum=" + phoneNum + ",医生id=" + doctorId + ",关联上级id=" + relationId + ",关联上级类型type="
                    + (relationType == 0 ? "商务代表" : "医生") + ",注册平台_p=" + platform);
            // 短信通知医生注册成功
            SMSUtil.sendSMS(phoneNum, SMSUtil.Template.DOCTOR_REGIS_H5_SUCCESS_TEMPLATE, name.trim(),
                    ConfigUtil.getString("doctor.app.name"), ConfigUtil.getString("doctor.appstore.download.url"));
        } else {
            LOGGER.info("医生通过app注册，医生手机号phoneNum=" + phoneNum + ",医生id=" + doctorId + ",注册平台_p=" + platform);
        }

        // 通知搜索服务，修改医生和代表的关系
        // 特定辉瑞代表二维码注册，自动标识辉瑞医生
        try {
            //Map<String, String> params = new HashMap<>();
            //params.put("doctorId", String.valueOf(doctorId));
            //RpcHttpUtils.asyncCallPost(NOTICE_SEARCH_RELATION, params);

            String pfizerBusinessId = ConfigUtil.getString(CommonConstant.PFIZER_BUSINESS_ID);
            if (CommonConstant.DOCTOR_SPREAD_TYPE_BUSINESS == relationType && StringUtils.equals(pfizerBusinessId, relationId.toString())) {
                RedisUtils.sadd(CommonConstant.LEGAL_PFIZER_DOCTOR_CACHE, doctorId.toString());
                LOGGER.info("[医生扫码注册] related by pfizer business ; doctorId={},pfizerBusinessId={}", doctorId, pfizerBusinessId);
            }
        } catch (Exception e) {

        }

        return doctorId;
    }

    @Override
    public void addDoctorLabel(LoginParamBO login, Long doctorId) {
        LOGGER.info("调用CMS服务给医生添加默认标签：doctorId = [{}], login=[{}]", doctorId, login.toString());
        // 医生注册辉瑞特殊代表标签处理
        //String pfizerBusinessId = ConfigUtil.getString(CommonConstant.PFIZER_BUSINESS_ID);
        //if (CommonConstant.DOCTOR_SPREAD_TYPE_BUSINESS == login.getRelationType() && StringUtils.equals(pfizerBusinessId,
        //        login.getRelationId().toString())) {
        //    StringBuilder labelTypes = new StringBuilder();
        //    labelTypes.append(",").append(Doctor.DOCTOR_LABEL_PFIZER);
        //    LOGGER.info("调用CMS服务给医生添加标签：doctorId = [{}], labelTypes=[{}]", doctorId, labelTypes);
        //    try {
        //        remoter.addDoctorLabel(doctorId, labelTypes.toString());
        //    } catch (BusinessException e) {
        //        LOGGER.error("调用添加医生默认标签服务异常：doctorId={}", doctorId);
        //    }
        //}
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long addUser(String phone, String name, String pwd) throws BusinessException {
        if (StringUtils.isBlank(pwd)) {
            pwd = "123456";
        }
        Integer relationType = LoginParamBO.RELATION_TYPE_PERSONAL;

        // 1. 验证手机是否存在
        this.validExistsPhone(phone);

        // 2.生成唯一的盐和邀请码
        String salt = null;
        String recommCode = null;
        do {
            salt = GengerCode.generateSaltCode();
            recommCode = GengerCode.generateDocCode();
        } while (this.findExsitSaltRecomm(salt, recommCode));

        // 3.判断注册渠道,并封装用户的注册信息
        Login login = this.validLogin(phone, pwd, relationType, 0L, salt, recommCode, -1);
        // 4.保存医生注册信息
        Long doctorId = null;
        try {
            doctorId = this.saveLoginInfo(login, name);
        } catch (Exception e) {
            LOGGER.error("保存医生注册数据异常，phone={}", phone, e);
            throw new BusinessException(ExceptionCodes.FAILED);
        }
        return doctorId;
    }

    @Override
    public void saveLogoutLog(Login login, Doctor doctor) {
        LoginLog loginLog = LoginLog.builder()
                .createdAt(new Date())
                .createdBy(login.getCreatedBy())
                .operate("退出登录")
                .deviceSN(login.getDeviceSN())
                .ip(login.getIp())
                .loginAddr(login.getLoginAddr())
                .loginTime(new Date())
                .model(login.getModel())
                .name(doctor.getName())
                .phone(doctor.getPhone())
                .type(LoginLog.TYPE_DOCTOR)
                .userId(doctor.getId())
                .build();
        this.loginLogRepository.save(loginLog);
    }

    @Override
    public void saveLoginLog(Login login, Doctor doctor) {
        LoginLog loginLog = LoginLog.builder()
                .createdAt(new Date())
                .createdBy(login.getCreatedBy())
                .operate("登录")
                .deviceSN(login.getDeviceSN())
                .ip(login.getIp())
                .loginAddr(login.getLoginAddr())
                .loginTime(login.getLastLoginTime())
                .model(login.getModel())
                .name(doctor.getName())
                .phone(doctor.getPhone())
                .type(LoginLog.TYPE_DOCTOR)
                .userId(doctor.getId())
                .build();
        this.loginLogRepository.save(loginLog);
    }

    @Override
    public LoginVO fastLoginInfoById(Long loginId, String loginKey, String fastLoginToken,  String version, String platform, String deviceSN, HttpServletRequest request) throws BusinessException {
        // 1.根据医生loginId获取登录信息
        Login login = this.findLoginById(loginId);
        if (login == null) {
            LOGGER.error("登陆获取医生数据异常,loginId={}", loginId);
            throw new BusinessException(ExceptionCodes.LOGIN_NOT_EXSIT);
        }

        String loginFailKey = PropertyValueConstants.getLoginFailKey(loginId);
        String loginFailValue = RedisUtils.getString(loginFailKey);
        if (StringUtils.isNotBlank(loginFailValue) && Integer.valueOf(loginFailValue) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
        }
        // 2.验证登陆key
        try {
            String cacheLoginKey = PropertyValueConstants.DOCTOR_LOGIN_KEY + String.valueOf(loginId);
            String cacheValue = (String) RedisUtils.get(cacheLoginKey);
            if (cacheValue == null || !cacheValue.equals(loginKey)) {
                LOGGER.error("登陆验证登陆key错误，客户端加密key=" + loginKey + "后台缓存加密后为:" + cacheValue + ",loginId=" + loginId);
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
            }
        } catch (Exception e) {
            LOGGER.error("向redis中缓存获取用户登陆token失败,loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT, e);
        }
        if (!checkFastLonginToken(loginId, fastLoginToken)) {
            RedisUtils.incre(loginFailKey);
            RedisUtils.expire(loginFailKey, PropertyValueConstants.LOGIN_FAIL_LOCK_TIME);
            if (Integer.valueOf(RedisUtils.getString(loginFailKey)) >= PropertyValueConstants.LOGIN_FAIL_COUNT) {
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
            }
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
        } else {
            RedisUtils.delete(loginFailKey);
        }

        // 3.如果医生信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, request);
    }

    /**
     *
     * @param doctorId
     * @param params ['经纪人ID', '大区ID', '省区ID']
     */
    @Override
    public void saveAgentRelation(Long doctorId, String[] params) {
        Date thisTime = new Date();
        AgAgentCustom custom = new AgAgentCustom();
        custom.setAgentId(Long.valueOf(params[0]));
        custom.setDoctorId(doctorId);
        custom.setAreaId(Long.valueOf(params[1]));
        custom.setProvinceAreaId(Long.valueOf(params[2]));
        custom.setStarLev(2);
        custom.setCreatedAt(thisTime);
        custom.setChangedAt(thisTime);
        custom.setCreatedBy("");
        custom.setChangedBy("");
        agentCustomRepository.save(custom);
    }

    /**
     * 生成快捷登录token
     *
     * @param doctorId 医生id
     * @return
     */
    private String generateFastLonginToken(Long doctorId) {
        try {
            DESPlus des = new DESPlus("leemenz");
            return des.encrypt(doctorId + "fast_login");
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 检查快捷登录token
     * @param doctorId
     * @param fastLoginToken
     * @return
     */
    private Boolean checkFastLonginToken(Long doctorId, String fastLoginToken) {
        try {
            DESPlus des = new DESPlus("leemenz");
            String decrypt = des.decrypt(fastLoginToken);
            if (decrypt.equals(doctorId + "fast_login")){
                return true;
            }else {
                return false;
            }
        } catch (Exception e) {
            return false;
        }
    }
}
